import type { BaseStorage, StorageItem, StorageOptions } from './types';

/**
 * IndexedDB 存储实现
 */
export class IndexedDB<T> implements BaseStorage<T> {
  private db: IDBDatabase | null = null;

  private readonly dbName: string;

  private readonly storeName: string;

  private readonly version: number;

  private readonly defaultTTL: number;

  private connecting: Promise<void> | null = null;

  private cleanupTimer: number | null = null;

  constructor(options: StorageOptions) {
    this.dbName = options.namespace;
    this.storeName = options.storeName || 'default';
    this.version = options.version || 1;
    this.defaultTTL = options.defaultTTL || 0;

    // 设置自动清理过期数据的定时器
    if (options.cleanupInterval && options.cleanupInterval > 0) {
      this.cleanupTimer = window.setInterval(() => {
        this.cleanupExpiredItems().catch((err) => {
          console.warn('清理过期数据失败:', err);
        });
      }, options.cleanupInterval);
    }
  }

  private connect(): Promise<void> {
    if (this.db) {
      return Promise.resolve();
    }

    if (this.connecting) {
      return this.connecting;
    }

    this.connecting = new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, this.version);

      request.onerror = (event) => {
        const error = new Error('无法连接到 IndexedDB');
        console.error('IndexedDB 连接失败:', event);
        reject(error);
      };

      request.onsuccess = () => {
        this.db = request.result;

        // 添加错误处理
        this.db.onerror = (event) => {
          console.error('IndexedDB 发生错误:', event);
        };

        resolve();
      };

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;

        // 创建存储对象
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName);
        }

        // 为旧版本升级到新版本添加迁移逻辑
        const { oldVersion } = event;
        const newVersion = this.version;

        if (oldVersion < newVersion) {
          console.error(`正在将数据库从版本 ${oldVersion} 升级到 ${newVersion}`);
          // 这里可以添加数据迁移逻辑
        }
      };
    });

    return this.connecting;
  }

  /**
   * 清理过期项目
   */
  private async cleanupExpiredItems(): Promise<void> {
    await this.connect();
    const now = Date.now();
    const allItems = (await this.getAll(true)) as Array<[string, StorageItem<T>]>;

    // 收集所有需要删除的键
    const keysToDelete = allItems.filter(([_, item]) => item.expireAt && item.expireAt < now).map(([key]) => key);

    // 并行删除所有过期项
    if (keysToDelete.length > 0) {
      await Promise.all(keysToDelete.map((key) => this.delete(key)));
    }
  }

  /**
   * 包装数据以支持TTL
   */
  private wrapItem(value: T, ttl?: number): StorageItem<T> {
    const expireAt = ttl || this.defaultTTL ? Date.now() + (ttl || this.defaultTTL) : undefined;
    return {
      value,
      expireAt,
    };
  }

  /**
   * 解包数据并检查过期状态
   * @returns 如果数据有效返回解包后的数据，否则返回null
   */
  private unwrapItem(item: StorageItem<T> | null): T | null {
    if (!item) return null;

    if (item.expireAt && item.expireAt < Date.now()) {
      return null; // 数据已过期
    }

    return item.value;
  }

  async set(key: string, value: T, ttl?: number): Promise<void> {
    await this.connect();
    return new Promise((resolve, reject) => {
      try {
        const transaction = this.db?.transaction([this.storeName], 'readwrite');
        const store = transaction.objectStore(this.storeName);
        const wrappedItem = this.wrapItem(value, ttl);
        const request = store.put(wrappedItem, key);

        request.onsuccess = () => resolve();
        request.onerror = (event) => {
          console.error('IndexedDB 写入失败:', event);
          reject(new Error('存储写入失败'));
        };

        transaction.onabort = (event) => {
          console.error('IndexedDB 事务中止:', event);
          reject(new Error('存储事务中止'));
        };
      } catch (error) {
        console.error('IndexedDB 操作异常:', error);
        reject(error);
      }
    });
  }

  async get(key: string): Promise<T | null> {
    await this.connect();
    return new Promise((resolve, reject) => {
      try {
        const transaction = this.db?.transaction([this.storeName], 'readonly');
        const store = transaction.objectStore(this.storeName);
        const request = store.get(key);

        request.onsuccess = () => {
          const item = request.result as StorageItem<T>;
          const value = this.unwrapItem(item);

          // 如果数据已过期，则异步删除
          if (item && item.expireAt && item.expireAt < Date.now()) {
            this.delete(key).catch((err) => {
              console.warn('删除过期数据失败:', err);
            });
          }

          resolve(value);
        };

        request.onerror = (event) => {
          console.error('读取数据失败:', event);
          reject(new Error('读取数据失败'));
        };
      } catch (error) {
        console.error('IndexedDB 操作异常:', error);
        reject(error);
      }
    });
  }

  async delete(key: string): Promise<void> {
    await this.connect();
    return new Promise((resolve, reject) => {
      try {
        const transaction = this.db?.transaction([this.storeName], 'readwrite');
        const store = transaction.objectStore(this.storeName);
        const request = store.delete(key);

        request.onsuccess = () => resolve();
        request.onerror = (event) => {
          console.error('删除数据失败:', event);
          reject(new Error('删除数据失败'));
        };
      } catch (error) {
        console.error('IndexedDB 操作异常:', error);
        reject(error);
      }
    });
  }

  /**
   * 获取所有数据
   * @param includeKeys 是否包含键
   * @returns 数据数组
   */
  async getAll(): Promise<T[]>;

  async getAll(includeKeys: boolean): Promise<T[] | Array<[string, StorageItem<T>]>>;

  async getAll(includeKeys = false): Promise<T[] | Array<[string, StorageItem<T>]>> {
    await this.connect();
    return new Promise((resolve, reject) => {
      try {
        const transaction = this.db?.transaction([this.storeName], 'readonly');
        const store = transaction.objectStore(this.storeName);
        const request = store.openCursor();

        const results: T[] = [];
        const keyValuePairs: Array<[string, StorageItem<T>]> = [];
        const now = Date.now();

        request.onsuccess = (event) => {
          const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;

          if (cursor) {
            const item = cursor.value as StorageItem<T>;
            const key = cursor.key as string;

            // 检查是否过期
            if (!item.expireAt || item.expireAt >= now) {
              if (includeKeys) {
                keyValuePairs.push([key, item]);
              } else {
                results.push(item.value);
              }
            }

            cursor.continue();
          } else {
            // 没有更多数据
            resolve(includeKeys ? keyValuePairs : results);
          }
        };

        request.onerror = (event) => {
          console.error('获取所有数据失败:', event);
          reject(new Error('获取所有数据失败'));
        };
      } catch (error) {
        console.error('IndexedDB 操作异常:', error);
        reject(error);
      }
    });
  }

  async query(predicate: (item: T) => boolean): Promise<T[]> {
    const allItems = await this.getAll();
    return allItems.filter(predicate);
  }

  async clear(): Promise<void> {
    await this.connect();
    return new Promise((resolve, reject) => {
      try {
        const transaction = this.db?.transaction([this.storeName], 'readwrite');
        const store = transaction.objectStore(this.storeName);
        const request = store.clear();

        request.onsuccess = () => resolve();
        request.onerror = (event) => {
          console.error('清空数据失败:', event);
          reject(new Error('清空数据失败'));
        };
      } catch (error) {
        console.error('IndexedDB 操作异常:', error);
        reject(error);
      }
    });
  }

  async has(key: string): Promise<boolean> {
    const value = await this.get(key);
    return value !== null;
  }

  async size(): Promise<number> {
    const items = (await this.getAll()) as T[];
    return items.length;
  }

  /**
   * 关闭数据库连接
   */
  close(): void {
    if (this.db) {
      this.db.close();
      this.db = null;
      this.connecting = null;
    }

    // 清除定时器
    if (this.cleanupTimer !== null) {
      window.clearInterval(this.cleanupTimer);
      this.cleanupTimer = null;
    }
  }

  /**
   * 检查 IndexedDB 是否可用
   */
  static isAvailable(): boolean {
    try {
      return typeof window !== 'undefined' && 'indexedDB' in window && window.indexedDB !== null;
    } catch {
      return false;
    }
  }

  /**
   * 删除数据库
   * @param dbName 数据库名称
   */
  static deleteDatabase(dbName: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.deleteDatabase(dbName);

      request.onsuccess = () => {
        // eslint-disable-next-line no-console
        console.log(`数据库 ${dbName} 已删除`);
        resolve();
      };

      request.onerror = (event) => {
        console.error(`删除数据库 ${dbName} 失败:`, event);
        reject(new Error('删除数据库失败'));
      };
    });
  }
}
